package middleware

import (
	"errors"
	"log"
	"mytest/models/request"
	"mytest/service/system"
	"net/http"
	"strconv"
	"time"

	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
)

//提供用户名和密码以获取令牌；并根据请求检查该令牌。
//首先我们要有一个签名
type JWT struct {
	SigningKey []byte
}

func NewJWT() *JWT {
	return &JWT{
		[]byte("ChenWei"),
	}
}

//创建一个token//加密算法是HS256
func (j *JWT) CreateToken(claims request.CustomClaims) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString(j.SigningKey)
}

//解析一个token
func (j *JWT) ParseToken(tokenString string) (*request.CustomClaims, error) {
	token, err := jwt.ParseWithClaims(tokenString, &request.CustomClaims{},
		func(token *jwt.Token) (i interface{}, e error) {
			return j.SigningKey, nil
		})
	if err != nil {
		if ve, ok := err.(jwt.ValidationError); ok {
			if ve.Errors&jwt.ValidationErrorMalformed != 0 {
				return nil, errors.New("That's not even a token")
			} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
				return nil, errors.New("Token is expired")
			} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
				return nil, errors.New("Token not active yet")

			} else {
				return nil, errors.New("Couldn't handle this token")

			}
		}
	}
	//tokenString -> *token
	if token != nil {
		//Valid ->有效
		if claims, ok := token.Claims.(*request.CustomClaims); ok && token.Valid {
			return claims, nil
		}
		return nil, errors.New("Couldn't handle this token:")
	} else {
		return nil, errors.New("Couldn't handle this token:")
	}
}

//更新一个token
//检验token
func JWTAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		token := c.Request.Header.Get("token")
		if token == "" {
			c.String(http.StatusOK, "未登录或非法访问")
			c.Abort()
			return
		}
		//查不到用户，加入黑名单
		//用户退出 加入黑名单
		//刷新token 加入黑名单
		//多点登陆，如何能在redis 取得出来，就加入黑名单，发新token
		//查一下黑名单
		if has, err := system.IsBlacklist(token); err != nil || has {
			log.Println("err", err, has, "您的帐户异地登陆或令牌失效:", token)
			c.String(http.StatusOK, "您的帐户异地登陆或令牌失效:%v", token)
			c.Abort()
			return
		}
		j := NewJWT()
		// parseToken 解析token包含的信息
		claims, err := j.ParseToken(token)
		if err != nil {
			c.String(http.StatusOK, "未登录或非法访问%v", err.Error())
			c.Abort()
			return
		}
		//最后一天的期间可以重新颁发token
		if claims.ExpiresAt-time.Now().Unix() < claims.BufferTime {
			claims.ExpiresAt = time.Now().Unix() + 60*60*24*7
			newtoken, _ := j.CreateToken(*claims)
			newClaims, _ := j.ParseToken(newtoken)
			c.Header("new-token", newtoken)
			c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt, 10))

			//下面代码为了单点登录，替换redis的token,把以前的加入黑名单
			//redis取出。取不出 就说明redis有问题
			err, jwtstr := system.GetRedisJWT(newClaims.Username)
			if err != nil {
				log.Println("jwt key in redis is failed", newClaims.Username)
			} else {
				if err = system.JornBlackList(jwtstr); err != nil {
					log.Println(err)
					c.Abort()
					return
				}
			}
			_ = system.SetRedisJWT(newClaims.Username, newtoken)
		}
		c.Set("claims", claims) //装入context，以后好用解析
		c.Next()
	}

}
